iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
JavaScript

React 觀念架構:從js 基礎到Hook 底層邏輯 系列 第 10 篇

Day 10 Diffing Algorithm 拆解:為什麼列表渲染要給 key?

  • 分享至 

  • xImage
  •  

一、 Diffing 演算法的三大策略

React 的 Reconciliation 演算法能將樹比對的複雜度降至 O(n),全靠以下三大策略:

  1. Tree Diff(同層比對):React 只會比對同一層級的節點。如果一個節點跨層級移動,React 不會嘗試移動它,而是直接銷毀並重建。
  2. Component Diff(組件比對):如果組件型別相同,繼續比對子樹;如果型別不同,直接替換整個組件樹。
  3. Element Diff(元素與列表比對):同層級的一群子節點,React 會透過唯一的 key 來辨識身份。

二、 沒給 key 時,列表是如何更新的?

假設我們有一個列表:['A', 'B'],現在想在最前面插入 'X',變成 ['X', 'A', 'B']。
無 key 情況下的比對:若沒有 key,React 只能依序按索引 (Index) 進行比對:

  • 第 0 位置:原本是 'A',現在變成 'X'-> 修改內容,將 A 改為 X。
  • 第 1 位置:原本是 'B',現在變成 'A' -> 修改內容,將 B 改為 A。
  • 第 2 位置:原本沒有,現在有'B' -> 新增 DOM 節點 'B'。

原本只是「移動」兩個節點並「新增」一個節點,React 卻執行了 2 次 DOM 修改 + 1 次 DOM 新增。如果列表節點帶有 Input 輸入框或內部 State,這些狀態將全數錯位!

三、 給予唯一 key

當我們為每個項目提供唯一的 key 屬性時:

// 舊:[{ key: 'a' }, { key: 'b' }]
// 新:[{ key: 'x' }, { key: 'a' }, { key: 'b' }]

有唯一 key 的比對:
React 透過 Map 尋找對應的 key:

發現 key 'x' 是全新的 ->新建 DOM 節點 'X' 並插入最前面。

發現 key 'a' 與 'b' 已經存在-> 完全保留 DOM 節點與內部 State,僅做位置移動。

效能大幅提升,且節點內部的 State 也不會因為位置變更而錯亂!

四、 為什麼極度不推薦使用 index 作為 key?

很多初學者懶得找唯一識別碼,直接寫 items.map((item, index) => <li key={index}>),這會引發嚴重的渲染 Bug:
當列表發生反轉、排序或中間刪除時,項目的 index 就會改變!

  1. 0假設刪除了索引為 0 的第一項(原 A,現剩 B)。

  2. 對 React 而言,新的第一項(原 B)其 index 依然是 0!

  3. React 誤以為 key="0" 的元素沒被刪除,只是 Props 變了,於是保留了舊 index 0 節點內部的 Uncontrolled State(例如勾選框或 Input 輸入內容)。

  4. 結果:畫面上刪除了第一項,但勾選狀態卻留在了新的第一項身上!

五、 key 的最佳實踐守則

  1. 永遠使用資料庫 ID 或唯一雜湊:如 item.id 或 crypto.randomUUID()(資料建立時產生)。

  2. 絕不能在渲染時動態生成 key:寫成 key={Math.random()} 會導致每次 Re-render 的 key 都不同,React 將強制銷毀並重建所有 DOM 節點,效能直接雪崩。

  3. key 的範圍僅限於同層級兄弟節點 (Siblings):key 不需要全域唯一,只要在同一個父節點的同層級列表中唯一即可。


上一篇
Day 9 Virtual DOM 與 Reconciliation(協調算法):React 憑什麼那麼快?
下一篇
Day 11 命令式 vs. 聲明式:從 DOM 操作轉變為「UI 是 State 的函數」
系列文
React 觀念架構:從js 基礎到Hook 底層邏輯 共 15 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言